Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert

  • entities.geojson — Schulen, deren Geokoordinaten und Attribute: entity_id, capacity, und andere Attribute
  • entities.csv — Statistische Blöcke Berlins und optimierungsrelevante Attribute: entity_id, capacity
  • units.geojson — Statistische Blöcke Berlins, deren Geometrie und Attribute
  • units.csv — Statsitische Blöcke Berlins und optimierungsrelevante Attribute: unit_id, population, percentage_sgb
  • weights.csv — optimierungsrelevante Gewichte wie Fußwege / Spalten: entity_id, unit_id, weight, value
  • assignment.csv — eine initiale Zuordnung / Spalten: unit_id, entity_id

Bereits in anderen scripten wurde vorbereitet:

  • Fußwegen von einer großen Sichprobe von (Wohn-)Gebäuden zu allen Schulen wurden berechnet und in route_matrix.csv gespeichert
  • Die Stichprobe wurde in sampled_buildings.csv gespeichert

Die Daten werden wiefolgt vorbereitet:

  • pro Block werden die Anzahl der einzuschulenden Kinder mit Hilfe der Einwohnerzahlen nach Alter auf LOR-Ebene in EWR201512E_Matrix.csv hochgerechnet
    • Kinder des LOR werden Anteilig nach Einwohnerzahl des Blocks im Verhältnis zum LOR auf die Blöcke verteilt
  • es werden minimale, durchschnittliche und maximale Fußwege aus jedem Block errechnet

TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)

Laden der Daten

sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields
HKO_2015 = readOGR('output/HKO_2015.geojson', 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "output/HKO_2015.geojson", layer: "OGRGeoJSON"
with 306019 features
It has 4 fields

Schulwege

route_matrix = read_rds('output/route_matrix.rds')

Schulkapazitäten und Einwohnerzahler auf LOR-Ebene

kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
  Bezirk = col_character(),
  Schulnummer = col_character(),
  Schulname = col_character(),
  Plätze = col_integer(),
  Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
  .default = col_character(),
  ZEIT = col_integer(),
  STADTRAUM = col_integer(),
  E_E = col_number(),
  E_EM = col_number(),
  E_EW = col_number(),
  E_E50_55 = col_number(),
  E_E25U55 = col_number(),
  E_E55U65 = col_number(),
  E_E65U80 = col_number()
)
See spec(...) for full column specifications.

Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten

re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2) %>% cbind(over(re_schulstand, lor))
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
  group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"

Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?

re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
  rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"

Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:

bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
In Anmeldeliste, fehlt in Schulstand
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
In re_schulstand, fehlt in Anmeldeliste
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))

Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.

data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
    coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):

relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
 [1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12"
 [9] "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20"
[17] "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28"
[25] "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
07G01

07G02

07G03

07G05

07G06

07G07

07G10

07G12

07G13

07G14

07G15

07G16

07G17

07G18

07G19

07G20

07G21

07G22

07G23

07G24

07G25

07G26

07G27

07G28

07G29

07G30

07G31

07G32

07G34

07G35

07G36

07G37

Mapping Bezirk->LOR->Block

df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)

Sanity-Check: LORs und Blöcke im Bezirk

bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk

ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen

Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.

TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?

Plot der 6-Jährigen nach EWR201512E_Matrix.csv

Wir verwenden das mittel der 5- und 6-Jährigen.

TODO neue Daten von Torres? TODO Prognose?

relevant_ewr = einwohner_lor %>%
  select(RAUMID, E_E05_06, E_E06_07) %>%
  filter(RAUMID %in% relevant_lors$PLR) %>%
  # Schnitt der 5 und 6-Jährigen
  mutate(kids=(as.numeric(gsub(',','.',E_E06_07))+as.numeric(gsub(',','.',E_E05_06)))/2) %>% as.data.frame()

data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke, Strukturquote

Strukturquote = 0.9

kids_in_blks = relevant_blks %>%
  group_by(PLR) %>%
  mutate(EinwRatio = Einw/sum(Einw)) %>%
  ungroup %>%
  left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>%
  mutate(kids = EinwRatio*kids) %>%
  mutate(kids = Strukturquote*kids) %>% # Strukturquote
  select(BEZ, PLR, BLK, Einw, kids) %>%
  as.data.frame()
row.names(kids_in_blks) = kids_in_blks$BLK

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))

ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze

relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer

Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken

'Summe Kapas'
[1] "Summe Kapas"
Summe Kapas
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
Anmeldungen
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
Kids laut Statistik
kids_in_blks$kids %>% sum
[1] 2620.8
relevant_ewr$kids %>% sum
[1] 2912

Schulwege von Blöcken zu Schulen aggregieren

Für jedes Wohngebäude suchen wir den zugehörigen Block

residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
  left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)

Plot der relevanten Blöcke (mit Wohngebäuden)

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
  #geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks

Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule

data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
  geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1119   33
travel_matrix

Sozioökonomische Daten

Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes

UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON 
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')

ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections

wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == bez_id,]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)

gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
    intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
    intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
    data.frame(area=intersection_area) # FIXME how else to normalize?
    )
length(intersection)
[1] 1219
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks?
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)

Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?

sgbII_blk = intersection_data %>%
  group_by(BLK) %>%
  summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area)/100)
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Adjazenz von Blöcken

row.names(blk_in_bez) = blk_in_bez$BLK
buffeded_blk_in_bez = gBuffer(blk_in_bez, byid=T, width=40)
adjacency = gIntersects(gBuffer(blk_in_bez, byid=T, width=40), byid = T)
rownames(adjacency) = blk_in_bez$BLK
colnames(adjacency) = blk_in_bez$BLK

adjacency_df = adjacency %>% as.data.frame() %>% mutate(from=rownames(.)) %>%
  gather(to, connected, -from) %>% filter(connected) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(from_long=V1, from_lat=V2) %>% mutate(from=rownames(.))) %>%
  inner_join(spTransform(blk_in_bez, wgs84) %>% coordinates() %>% as.data.frame() %>% rename(to_long=V1, to_lat=V2) %>% mutate(to=rownames(.)))
Joining, by = "from"
Joining, by = "to"
ggplot() +
  geom_polygon(aes(x=long, y=lat, group=group), fill='gray', data=broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK')) + 
  geom_segment(aes(x=from_long, y=from_lat, xend=to_long, yend=to_lat), size=0.1, color='black', data=adjacency_df) +
  theme_nothing() + coord_map()
Warning: `panel.margin` is deprecated. Please use `panel.spacing` property
instead

ggsave('figs/adjacency.pdf')
Saving 7 x 5 in image
adjacency_df %>% filter(connected & from != to) %>% select(from, to) %>% write_csv('app/data/adjacency.csv')

Select relevant data

optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
[1] 1043
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)

optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]

dim(optim_matrix)
[1] 1043   32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2620.383

Naive Zuordnung: Jeder Block zur nächsten Schule

solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup

optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 824464.8
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
Joining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
  geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
  geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
  coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
  guides(color=F, fill=F)

Darstellung der Zuordnung als Tabelle

library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
  group_by(school) %>% summarise(
    kids=sum(kids),
    num_blocks=n(),
    min_dist=min(min),
    avg_dist=mean((kids*avg)/sum(kids)),
    max_dist=max(max)
  ) %>%
  inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
  mutate(
    utilization=kids/Kapa
  ) %>% select(
   Schule=school,
   `Blöcke`=num_blocks,
   Kapazität=Kapa,
   Kinder=kids,
   Auslastung=utilization,
   `Weg (min)`=min_dist,
   `Weg (Ø)`=avg_dist,
   `Weg (max)`=max_dist
  ) %>%
  formattable(
    list(
      Kinder = formatter("span", x ~ digits(x, 2)),
      Auslastung = formatter("span",
        style = x ~ style(color = ifelse(x < 1, "green", "red")),
        x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
      ),
      `Weg (Ø)` = proportion_bar("lightblue"),
      `Weg (min)` = proportion_bar("lightblue"),
      `Weg (max)` = proportion_bar("lightblue")
    )
  )
Schule Blöcke Kapazität Kinder Auslastung Weg (min) Weg (Ø) Weg (max)
07G01 14 66 64.76 98.12% 5.353920 650.8339 1427.7231
07G02 19 98 46.82 47.77% 8.950325 554.4286 1180.4888
07G03 14 73 52.70 72.19% 28.978802 491.9032 1218.2517
07G05 21 78 106.35 136.35% 27.914349 584.4624 1160.0463
07G06 29 55 79.84 145.17% 21.638306 819.4894 1423.5819
07G07 14 81 25.24 31.16% 2.135248 788.8354 1921.8030
07G10 29 112 124.52 111.18% 9.792883 669.8382 1790.9264
07G12 10 75 27.84 37.12% 11.444611 334.1917 620.2274
07G13 26 82 125.58 153.15% 2.865692 490.8702 1267.9886
07G14 37 78 89.35 114.55% 3.249405 451.1208 860.7422
07G15 50 104 207.44 199.46% 0.404785 827.6395 1606.6709
07G16 20 104 53.28 51.24% 27.838060 544.4797 1137.7349
07G17 24 100 68.77 68.77% 6.657265 397.3450 830.8367
07G18 19 44 65.61 149.11% 0.331778 364.6071 966.1512
07G19 39 112 107.61 96.08% 4.991144 1102.5844 2304.9932
07G20 46 81 153.09 189.00% 78.442825 730.8813 1861.3053
07G21 23 72 71.75 99.66% 58.807545 518.8256 2055.9846
07G22 26 91 60.33 66.30% 5.131943 571.4961 1394.3684
07G23 10 50 21.98 43.96% 2.349617 822.4686 1966.9396
07G24 33 75 72.55 96.73% 68.448639 656.9605 1660.2349
07G25 40 75 128.22 170.96% 0.000000 639.8039 2220.8682
07G26 25 52 31.91 61.36% 8.994896 549.4798 1357.8990
07G27 17 75 41.15 54.87% 1.954395 691.5387 1432.6587
07G28 69 75 92.47 123.29% 52.458755 1037.4539 2913.8135
07G29 92 104 109.94 105.71% 1.030483 934.9748 2611.7969
07G30 46 72 74.29 103.18% 0.896759 879.4613 2514.0334
07G31 23 78 51.14 65.57% 0.000000 894.3747 1845.4099
07G32 58 78 57.77 74.06% 2.878403 762.9779 1462.1539
07G34 38 100 168.79 168.79% 0.000000 1150.4233 2136.8945
07G35 36 75 92.97 123.97% 3.014389 786.9284 1526.0809
07G36 37 100 57.20 57.20% 6.966839 977.6602 2434.5454
07G37 59 69 89.11 129.15% 3.201996 1260.8585 2305.8303

Daten für die App speichern

  • entities.geojson
  • entities.csv
  • units.geojson
  • units.csv
  • weights.csv
  • assignment.csv

Neue Daten

Entities / Schulen

entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
  rename(entity_id = spatial_name) %>%
  select(-gml_id, -spatial_alias, -spatial_type) %>%
  inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
[1] TRUE
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')

Units / Statistische Blöcke

units = subset(blk, BEZ == bez_id)

units@data$PLR = NULL
units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL

units = units %>% sp::merge(
  optim_kids_in_blks %>%
    left_join(sgbII_blk) %>%
    select(unit_id=BLK, population=kids, sgbIIu65)
  )
Joining, by = "BLK"
file.remove('app/data/units.geojson')
[1] TRUE
units %>% writeOGR('app/data/units.geojson', layer="units", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')

Zuordnung

assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')

Weights / Schulwege

travel_from_blks %>%
  rename(unit_id=BLK, entity_id=dst) %>%
  #gather(weight, value, -unit_id, -entity_id) %>%
  write_csv('app/data/weights.csv')

Zusätzliche Daten

file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
[1] FALSE
addresses_in_bez = HKO_2015[!is.na(over(HKO_2015, subset(blk, BEZ == bez_id))$BEZ),c('STN', 'HNR')]
names(addresses_in_bez) = c('street', 'no')
addresses_in_bez@data
file.remove('app/data/addresses.geojson')
[1] TRUE
writeOGR(addresses_in_bez, "app/data/addresses.geojson", layer="addresses", driver="GeoJSON", check_exists = FALSE)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IFRSVUUKICAgIHRvY19mbG9hdDogVFJVRQotLS0KCmBgYHtyIGxpYnMsIGluY2x1ZGU9Riwgd2FybmluZz1GfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShyZ2RhbCkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdnbWFwKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KG1hcHRvb2xzKQpsaWJyYXJ5KHJnZW9zKQpgYGAKCkRpZXNlcyBOb3RlYm9vayBiZXJlaXRldCBkaWUgRGF0ZW4gZsO8ciBkaWUgSW50ZWxsaWdlbnQgWm9uaW5nIEVuZ2luZSB2b3IuIEVzIHNwZWljaGVydAoKLSBlbnRpdGllcy5nZW9qc29uIOKAlCBTY2h1bGVuLCBkZXJlbiBHZW9rb29yZGluYXRlbiB1bmQgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5LCB1bmQgYW5kZXJlIEF0dHJpYnV0ZQotIGVudGl0aWVzLmNzdiDigJQgU3RhdGlzdGlzY2hlIEJsw7Zja2UgQmVybGlucyB1bmQgb3B0aW1pZXJ1bmdzcmVsZXZhbnRlIEF0dHJpYnV0ZTogZW50aXR5X2lkLCBjYXBhY2l0eQotIHVuaXRzLmdlb2pzb24g4oCUIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlIEJlcmxpbnMsIGRlcmVuIEdlb21ldHJpZSB1bmQgQXR0cmlidXRlCi0gdW5pdHMuY3N2IOKAlCBTdGF0c2l0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiB1bml0X2lkLCBwb3B1bGF0aW9uLCBwZXJjZW50YWdlX3NnYgotIHdlaWdodHMuY3N2IOKAlCBvcHRpbWllcnVuZ3NyZWxldmFudGUgR2V3aWNodGUgd2llIEZ1w593ZWdlIC8gU3BhbHRlbjogZW50aXR5X2lkLCB1bml0X2lkLCB3ZWlnaHQsIHZhbHVlCi0gYXNzaWdubWVudC5jc3Yg4oCUIGVpbmUgaW5pdGlhbGUgWnVvcmRudW5nIC8gU3BhbHRlbjogdW5pdF9pZCwgZW50aXR5X2lkCgpCZXJlaXRzIGluIGFuZGVyZW4gc2NyaXB0ZW4gd3VyZGUgdm9yYmVyZWl0ZXQ6CgotIEZ1w593ZWdlbiB2b24gZWluZXIgZ3Jvw59lbiBTaWNocHJvYmUgdm9uIChfV29obl8tKUdlYsOkdWRlbiB6dSBhbGxlbiBTY2h1bGVuIHd1cmRlbiBiZXJlY2huZXQgdW5kIGluIGByb3V0ZV9tYXRyaXguY3N2YCBnZXNwZWljaGVydAotIERpZSBTdGljaHByb2JlIHd1cmRlIGluIGBzYW1wbGVkX2J1aWxkaW5ncy5jc3ZgIGdlc3BlaWNoZXJ0CgpEaWUgRGF0ZW4gd2VyZGVuIHdpZWZvbGd0IHZvcmJlcmVpdGV0OgoKLSBwcm8gQmxvY2sgd2VyZGVuIGRpZSBBbnphaGwgZGVyIGVpbnp1c2NodWxlbmRlbiBLaW5kZXIgbWl0IEhpbGZlIGRlciBFaW53b2huZXJ6YWhsZW4gbmFjaCBBbHRlciBhdWYgTE9SLUViZW5lIGluIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgIGhvY2hnZXJlY2huZXQKICAgIC0gS2luZGVyIGRlcyBMT1Igd2VyZGVuIEFudGVpbGlnIG5hY2ggRWlud29obmVyemFobCBkZXMgQmxvY2tzIGltIFZlcmjDpGx0bmlzIHp1bSBMT1IgYXVmIGRpZSBCbMO2Y2tlIHZlcnRlaWx0Ci0gZXMgd2VyZGVuIG1pbmltYWxlLCBkdXJjaHNjaG5pdHRsaWNoZSB1bmQgbWF4aW1hbGUgRnXDn3dlZ2UgYXVzIGplZGVtIEJsb2NrIGVycmVjaG5ldAoKVE9ETzoKLSBkaWUgc296aW/Dtmtvbm9taXNjaGVuIEZha3RvcmVuIHdlcmRlbiBhdXMgZGVuIFdhaGxiZXppcmtlbiBhdWYgZGllIEJsw7Zja2UgaG9jaGdlcmVjaG5ldCAoaHR0cHM6Ly9naXRodWIuY29tL2JlcmxpbmVybW9yZ2VucG9zdC9jb2dyYW4pCgojIyBMYWRlbiBkZXIgRGF0ZW4KCmBgYHtyIGxvYWQgZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc2FtcGxlZF9idWlsZGluZ3MgPSByZWFkX3Jkcygnb3V0cHV0L3NhbXBsZWRfYnVpbGRpbmdzLnJkcycpCmJleiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9CRVpfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYmxrID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JMS18yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpsb3IgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfTE9SXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnJlX3NjaHVsc3RhbmQgPSByZWFkT0dSKCdkb3dubG9hZC9yZV9zY2h1bHN0YW5kLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpIS09fMjAxNSA9IHJlYWRPR1IoJ291dHB1dC9IS09fMjAxNS5nZW9qc29uJywgJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKSAlPiUgY2JpbmQob3ZlcihyZV9zY2h1bHN0YW5kLCBsb3IpKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmICFpcy5uYShgUGzDpHR6ZWApKSAlPiUgLiRzcGF0aWFsX25hbWUKcmVsZXZhbnRfc2Nob29scwpgYGAKCiMjIE1hcHBpbmcgQmV6aXJrLT5MT1ItPkJsb2NrCgpgYGB7cn0KZGZfYmV6ID0gYXMuZGF0YS5mcmFtZShiZXopCmRmX2xvciA9IGFzLmRhdGEuZnJhbWUobG9yKQpkZl9ibGsgPSBhcy5kYXRhLmZyYW1lKGJsaykKYGBgCgojIyMgU2FuaXR5LUNoZWNrOiBMT1JzIHVuZCBCbMO2Y2tlIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWxvcltsb3IkQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6LCBjb2xvcj0ncmVkJykKYGBgCgojIyMgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJsa1tibGskQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6W2JleiRCRVogPT0gYmV6X2lkLF0sIGNvbG9yPSdyZWQnKQpgYGAKCiMjIEtpbmRlciBpbSBCZXppcmsgYXVmIEJsw7Zja2UgaG9jaHJlY2huZW4KCsOcYmVyIGRpZSBFaW53b2huZXJpbmZvcm1hdGlvbmVuIGluIGBSQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbmAga2FubiBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YCB2b24gTE9SLUViZW5lIGF1ZiBCbG9ja2ViZW5lIGhvY2hnZXJlY2huZXQgd2VyZGVuLgoKVE9ETzogU3RhdHRkZXNzZW4gbWl0IGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJsaW5lcm1vcmdlbnBvc3QvY29ncmFuIG1hY2hlbj8KCiMjIyBQbG90IGRlciA2LUrDpGhyaWdlbiBuYWNoIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgCgpXaXIgdmVyd2VuZGVuIGRhcyBtaXR0ZWwgZGVyIDUtIHVuZCA2LUrDpGhyaWdlbi4KClRPRE8gbmV1ZSBEYXRlbiB2b24gVG9ycmVzPwpUT0RPIFByb2dub3NlPwoKYGBge3J9CnJlbGV2YW50X2V3ciA9IGVpbndvaG5lcl9sb3IgJT4lCiAgc2VsZWN0KFJBVU1JRCwgRV9FMDVfMDYsIEVfRTA2XzA3KSAlPiUKICBmaWx0ZXIoUkFVTUlEICVpbiUgcmVsZXZhbnRfbG9ycyRQTFIpICU+JQogICMgU2Nobml0dCBkZXIgNSB1bmQgNi1Kw6RocmlnZW4KICBtdXRhdGUoa2lkcz0oYXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSthcy5udW1lcmljKGdzdWIoJywnLCcuJyxFX0UwNV8wNikpKS8yKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpkYXRhID0gdGlkeShsb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdQTFInKSAlPiUgaW5uZXJfam9pbihyZWxldmFudF9ld3IsIGJ5PWMoJ2lkJz0nUkFVTUlEJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIyBQbG90IGRlciBFaW53b2huZXIgYXVmIEJsb2NrZWJlbmUKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihkZl9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoRWludz1pZmVsc2UoRWludz09MCwgTkEsIEVpbncpKQowCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPUVpbncpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgojIyMgSG9jaHJyZWNobnVuZyBhdWYgQmzDtmNrZSwgU3RydWt0dXJxdW90ZQoKYGBge3J9ClN0cnVrdHVycXVvdGUgPSAwLjkKCmtpZHNfaW5fYmxrcyA9IHJlbGV2YW50X2Jsa3MgJT4lCiAgZ3JvdXBfYnkoUExSKSAlPiUKICBtdXRhdGUoRWlud1JhdGlvID0gRWludy9zdW0oRWludykpICU+JQogIHVuZ3JvdXAgJT4lCiAgbGVmdF9qb2luKHJlbGV2YW50X2V3ciwgYnk9YygnUExSJz0nUkFVTUlEJykpICU+JQogIG11dGF0ZShraWRzID0gRWlud1JhdGlvKmtpZHMpICU+JQogIG11dGF0ZShraWRzID0gU3RydWt0dXJxdW90ZSpraWRzKSAlPiUgIyBTdHJ1a3R1cnF1b3RlCiAgc2VsZWN0KEJFWiwgUExSLCBCTEssIEVpbncsIGtpZHMpICU+JQogIGFzLmRhdGEuZnJhbWUoKQpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcywgYnk9YygnaWQnPSdCTEsnKSkgJT4lIG11dGF0ZShraWRzPWlmZWxzZShraWRzPT0wLCBOQSwga2lkcyksIEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIFZlcmbDvGdiYXJlIFBsw6R0emUKCmBgYHtyfQpyZWxldmFudF9rYXBhcyA9IGthcGFzICU+JSBzZWxlY3QoU2NodWxudW1tZXIsIEthcGE9YFBsw6R0emVgKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIGFzLmRhdGEuZnJhbWUoKQojcm93Lm5hbWVzKHJlbGV2YW50X2thcGFzKSA9IHJlbGV2YW50X2thcGFzJFNjaHVsbnVtbWVyCmBgYAoKIyMjIMOcYmVycHLDvGZ1bmcgZGVyIFN1bW1lIGRlciBLYXBheml0w6R0ZW4sIEFubWVsZHVuZ2VuIHVuZCBLaW5kZXJzdGF0aXN0aWtlbgoKYGBge3J9CidTdW1tZSBLYXBhcycKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVfbWF0cml4ICU+JSBmaWx0ZXIoZHN0ICVpbiUgcmVsZXZhbnRfc2Nob29scyksIGJ5PWMoJ09JJz0nc3JjJykpCmhlYWQocm91dGVzX2Zyb21fYmxrcykKYGBgCgojIyMgUGxvdCBkZXIgcmVsZXZhbnRlbiBCbMO2Y2tlIChtaXQgV29obmdlYsOkdWRlbikKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihyb3V0ZXNfZnJvbV9ibGtzICU+JSBncm91cF9ieShCTEspICU+JSBzdW1tYXJpc2Uobj1uKCkpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9ZGF0YSkgKwogICNnZW9tX3BvaW50KGFlcyh4PWxvbiwgeT1sYXQpLCBkYXRhPXJiX2RmLCBjb2xvcj0nYmxhY2snLCBzaXplPTAuMDEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIyBTb3ppb8O2a29ub21pc2NoZSBEYXRlbgoKV2lyIGhhYmVuIFNvemlvw7Zrb25vbWlzY2hlIERhdGVuIGluIGRlbiBXYWhsYmV6aXJrZW4uIFN0cmF0ZWdpZToKLSBTY2huZWlkZW4gZGVyIFdhaGxiZXppcmtlIG1pdCBkZW4gQmzDtmNrZW4KLSDDnGJlcm5haG1lIGRlcyBQcm96ZW50d2VydGVzIHZvbSBXYWhsYmV6aXJrIGbDvHIgamVkZW4gKFVudGVyLSlibG9jawotIFp1b3JkbnVuZyB6dSBqZWRlbSBCbG9jayB1bmQgVmVyZWluaWd1bmcgZHVyY2ggRmzDpGNoZW4vV29obmhhdXMtZ2V3aWNodGV0ZXMgTWl0dGVsIGRlcyBQcm96ZW50d2VydGVzIAoKYGBge3J9ClVXQiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9VV0JfQUdIMjAxNi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKVVdCJElEID0gcGFzdGUwKFVXQiRCRVosIFVXQiRVV0IpCnNvemlvX1VXQiA9IHJlYWRfZXhjZWwoJ2Rvd25sb2FkL0RMX0JFX0FIMjAxNl9TdHJ1a3R1cmRhdGVuLnhsc3gnLCBzaGVldCA9IDMpICU+JSBzZWxlY3QoSUQsIHNnYklJdTY1PWBFaW53b2huZXIgdW50ZXIgNjUgaW4gU0dCIElJIDIwMTQgUHJvemVudGApClVXQiA9IFVXQiAlPiUgc3A6Om1lcmdlKHNvemlvX1VXQiwgYnk9J0lEJykKCmdncGxvdChicm9vbTo6dGlkeShVV0IsIHJlZ2lvbj0nSUQnKSAlPiUgaW5uZXJfam9pbihVV0IgJT4lIGFzLmRhdGEuZnJhbWUsIGJ5PWMoJ2lkJz0nSUQnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgpDaGVjayBmb3Igc2VsZiBpbnRlcnNlY3Rpb25zCmBgYHtyfQp3Z3M4NCA9IENSUyhwcm9qNHN0cmluZyhibGspKQplYV9wcm9qZWN0aW9uID0gQ1JTKCIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyIpCmJsa19pbl9iZXogPSBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXQpnSXNWYWxpZChibGtfaW5fYmV6KQpibGtfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aCA9IC0wLjEpCmFueSh4b3IoZ0ludGVyc2VjdHMoYmxrX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aChibGtfaW5fYmV6KSkgPT0gMSkpCnBsb3QoYmxrX2luX2JleikKI2dJbnRlcnNlY3RzKFVXQltVV0IkQkVaID09ICcwNycsXSwgYnlpZD1UUlVFKQpgYGAKCmBgYHtyfQp1d2JfaW5fYmV6ID0gZ0J1ZmZlcihzcFRyYW5zZm9ybShVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGVhX3Byb2plY3Rpb24pLCBieWlkPVQsIHdpZHRoPS0wLjEpCmdJc1ZhbGlkKHV3Yl9pbl9iZXopCmFueSh4b3IoZ0ludGVyc2VjdHModXdiX2luX2JleiwgYnlpZD1ULCByZXR1cm5EZW5zZSA9IFQpLCBkaWFnKDEsIGxlbmd0aCh1d2JfaW5fYmV6KSkgPT0gMSkpCnBsb3QodXdiX2luX2JleikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uID0gZ0ludGVyc2VjdGlvbih1d2JfaW5fYmV6LCBibGtfaW5fYmV6LCBieWlkID0gVCwgZHJvcF9sb3dlcl90ZCA9IFQpCgpnSXNWYWxpZChpbnRlcnNlY3Rpb24pCnBsb3QoaW50ZXJzZWN0aW9uKQpgYGAKCmBgYHtyfQppbnRlcnNlY3Rpb25fdXdiX2RhdGEgPSBpbnRlcnNlY3Rpb24gJT4lIG92ZXIodXdiX2luX2JleikKaW50ZXJzZWN0aW9uX2Jsa19kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKGJsa19pbl9iZXopCmludGVyc2VjdGlvbl9hcmVhID0gZ0FyZWEoaW50ZXJzZWN0aW9uLCBieWlkPVQpCmludGVyc2VjdGlvbl9kYXRhID0gY2JpbmQoCiAgICBpbnRlcnNlY3Rpb25fdXdiX2RhdGEgJT4lIHNlbGVjdChVV0JfSUQ9SUQsIHNnYklJdTY1KSwKICAgIGludGVyc2VjdGlvbl9ibGtfZGF0YSAlPiUgc2VsZWN0KEJMSywgQkxLX0Vpbnc9RWludyksCiAgICBkYXRhLmZyYW1lKGFyZWE9aW50ZXJzZWN0aW9uX2FyZWEpICMgRklYTUUgaG93IGVsc2UgdG8gbm9ybWFsaXplPwogICAgKQpgYGAKCmBgYHtyfQpsZW5ndGgoaW50ZXJzZWN0aW9uKQpucm93KGJsa19pbl9iZXopCiMgZGlkIHdlIG1pc3MgYW55IGJsb2Nrcz8Kc2V0ZGlmZihibGtfaW5fYmV6JEJMSywgaW50ZXJzZWN0aW9uX2RhdGEkQkxLKQpgYGAKClBybyBCbG9jayBtaXNjaGUgZGllIFNHQi1XZXJ0ZSBkZXIgVW50ZXJibMO2Y2tlIGdld2ljaHRldCBuYWNoIEZsw6RjaGUuCkZJWE1FIC0gYmVzc2VyIG5hY2ggQW56YWhsIGRlciBXb2huaMOkdXNlcj8KYGBge3J9CnNnYklJX2JsayA9IGludGVyc2VjdGlvbl9kYXRhICU+JQogIGdyb3VwX2J5KEJMSykgJT4lCiAgc3VtbWFyaXNlKHNnYklJdTY1PXN1bShzZ2JJSXU2NSphcmVhKS9zdW0oYXJlYSkvMTAwKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNnYklJX2JsaywgYnk9YygnaWQnPSdCTEsnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgojIyBBZGphemVueiB2b24gQmzDtmNrZW4KCmBgYHtyfQpyb3cubmFtZXMoYmxrX2luX2JleikgPSBibGtfaW5fYmV6JEJMSwpidWZmZWRlZF9ibGtfaW5fYmV6ID0gZ0J1ZmZlcihibGtfaW5fYmV6LCBieWlkPVQsIHdpZHRoPTQwKQphZGphY2VuY3kgPSBnSW50ZXJzZWN0cyhnQnVmZmVyKGJsa19pbl9iZXosIGJ5aWQ9VCwgd2lkdGg9NDApLCBieWlkID0gVCkKcm93bmFtZXMoYWRqYWNlbmN5KSA9IGJsa19pbl9iZXokQkxLCmNvbG5hbWVzKGFkamFjZW5jeSkgPSBibGtfaW5fYmV6JEJMSwoKYWRqYWNlbmN5X2RmID0gYWRqYWNlbmN5ICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSAlPiUKICBnYXRoZXIodG8sIGNvbm5lY3RlZCwgLWZyb20pICU+JSBmaWx0ZXIoY29ubmVjdGVkKSAlPiUKICBpbm5lcl9qb2luKHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSAlPiUgY29vcmRpbmF0ZXMoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByZW5hbWUoZnJvbV9sb25nPVYxLCBmcm9tX2xhdD1WMikgJT4lIG11dGF0ZShmcm9tPXJvd25hbWVzKC4pKSkgJT4lCiAgaW5uZXJfam9pbihzcFRyYW5zZm9ybShibGtfaW5fYmV6LCB3Z3M4NCkgJT4lIGNvb3JkaW5hdGVzKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcmVuYW1lKHRvX2xvbmc9VjEsIHRvX2xhdD1WMikgJT4lIG11dGF0ZSh0bz1yb3duYW1lcyguKSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J2dyYXknLCBkYXRhPWJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSkgKyAKICBnZW9tX3NlZ21lbnQoYWVzKHg9ZnJvbV9sb25nLCB5PWZyb21fbGF0LCB4ZW5kPXRvX2xvbmcsIHllbmQ9dG9fbGF0KSwgc2l6ZT0wLjEsIGNvbG9yPSdibGFjaycsIGRhdGE9YWRqYWNlbmN5X2RmKSArCiAgdGhlbWVfbm90aGluZygpICsgY29vcmRfbWFwKCkKCmdnc2F2ZSgnZmlncy9hZGphY2VuY3kucGRmJykKYWRqYWNlbmN5X2RmICU+JSBmaWx0ZXIoY29ubmVjdGVkICYgZnJvbSAhPSB0bykgJT4lIHNlbGVjdChmcm9tLCB0bykgJT4lIHdyaXRlX2NzdignYXBwL2RhdGEvYWRqYWNlbmN5LmNzdicpCmBgYAoKCiMjIFNlbGVjdCByZWxldmFudCBkYXRhCgpgYGB7cn0Kb3B0aW1fa2FwYXMgPSByZWxldmFudF9rYXBhcwpvcHRpbV9raWRzX2luX2Jsa3MgPSBraWRzX2luX2Jsa3MgJT4lIGZpbHRlcihraWRzID4gMCkgJT4lIGlubmVyX2pvaW4odHJhdmVsX21hdHJpeCwgYnk9J0JMSycpICU+JSBzZWxlY3QoQkxLLCBraWRzKSAlPiUgbXV0YXRlKGtpZHM9a2lkcykKbnJvdyhvcHRpbV9raWRzX2luX2Jsa3MpCm5yb3cob3B0aW1fa2FwYXMpCgpzZWxlY3Rfc2Nob29scyA9IGFzLmNoYXJhY3RlcihvcHRpbV9rYXBhcyRTY2h1bG51bW1lcikKc2VsZWN0X2Jsa3MgPSBhcy5jaGFyYWN0ZXIob3B0aW1fa2lkc19pbl9ibGtzJEJMSykKCm9wdGltX21hdHJpeCA9IGlubmVyX2pvaW4ob3B0aW1fa2lkc19pbl9ibGtzLCB0cmF2ZWxfbWF0cml4LCBieT0nQkxLJylbc2VsZWN0X3NjaG9vbHNdCgpkaW0ob3B0aW1fbWF0cml4KQoKb3B0aW1fa2FwYXMkS2FwYSAlPiUgc3VtCm9wdGltX2tpZHNfaW5fYmxrcyRraWRzICU+JSBzdW0KYGBgCgojIyBOYWl2ZSBadW9yZG51bmc6IEplZGVyIEJsb2NrIHp1ciBuw6RjaHN0ZW4gU2NodWxlCgpgYGB7cn0Kc29sdXRpb24gPSBvcHRpbV9tYXRyaXggJT4lIG11dGF0ZShCTEs9b3B0aW1fa2lkc19pbl9ibGtzJEJMSykgJT4lIGdhdGhlcihzY2hvb2wsIGRpc3QsIC1CTEspICU+JSBncm91cF9ieShCTEspICU+JSB0b3BfbigxLCAtZGlzdCkgJT4lIHVuZ3JvdXAKCm9wdGltX21hdHJpeCAlPiUgdCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc3VtbWFyaXNlX2VhY2goZnVucyhtaW4pKSAlPiUgc3VtKCkKCnNvbGluZXMgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkgJT4lIGlubmVyX2pvaW4oY2JpbmQoYXMuZGF0YS5mcmFtZShjb29yZGluYXRlcyhibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSkpLCBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXUBkYXRhWydCTEsnXSkpCgpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNvbHV0aW9uLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXAsIGRhcmtlbiA9IGMoMC41LCAnd2hpdGUnKSkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPXNjaG9vbCksIGRhdGE9ZGF0YSkgKwogIGdlb21fc2VnbWVudChhZXMoeD1WMSx5PVYyLHhlbmQ9bG9uLHllbmQ9bGF0KSwgZGF0YT1zb2xpbmVzLCBzaXplPTAuMykgKwogIGdlb21fcG9pbnQoYWVzKGxvbiwgbGF0KSwgY29sb3I9J2JsYWNrJywgc2l6ZT0yLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQsIGNvbG9yPXNwYXRpYWxfbmFtZSksIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKSArCiAgZ3VpZGVzKGNvbG9yPUYsIGZpbGw9RikKYGBgCgojIyBEYXJzdGVsbHVuZyBkZXIgWnVvcmRudW5nIGFscyBUYWJlbGxlCgpgYGB7cn0KbGlicmFyeShmb3JtYXR0YWJsZSkKYGBgCgpgYGB7cn0Kc29sdXRpb24gJT4lIGlubmVyX2pvaW4ob3B0aW1fa2lkc19pbl9ibGtzLCBieT0nQkxLJykgJT4lIGlubmVyX2pvaW4odHJhdmVsX2Zyb21fYmxrcywgYnk9YygnQkxLJz0nQkxLJywgJ3NjaG9vbCc9J2RzdCcpKSAlPiUKICBncm91cF9ieShzY2hvb2wpICU+JSBzdW1tYXJpc2UoCiAgICBraWRzPXN1bShraWRzKSwKICAgIG51bV9ibG9ja3M9bigpLAogICAgbWluX2Rpc3Q9bWluKG1pbiksCiAgICBhdmdfZGlzdD1tZWFuKChraWRzKmF2Zykvc3VtKGtpZHMpKSwKICAgIG1heF9kaXN0PW1heChtYXgpCiAgKSAlPiUKICBpbm5lcl9qb2luKHJlbGV2YW50X2thcGFzLCBieT1jKCdzY2hvb2wnPSdTY2h1bG51bW1lcicpKSAlPiUKICBtdXRhdGUoCiAgICB1dGlsaXphdGlvbj1raWRzL0thcGEKICApICU+JSBzZWxlY3QoCiAgIFNjaHVsZT1zY2hvb2wsCiAgIGBCbMO2Y2tlYD1udW1fYmxvY2tzLAogICBLYXBheml0w6R0PUthcGEsCiAgIEtpbmRlcj1raWRzLAogICBBdXNsYXN0dW5nPXV0aWxpemF0aW9uLAogICBgV2VnIChtaW4pYD1taW5fZGlzdCwKICAgYFdlZyAow5gpYD1hdmdfZGlzdCwKICAgYFdlZyAobWF4KWA9bWF4X2Rpc3QKICApICU+JQogIGZvcm1hdHRhYmxlKAogICAgbGlzdCgKICAgICAgS2luZGVyID0gZm9ybWF0dGVyKCJzcGFuIiwgeCB+IGRpZ2l0cyh4LCAyKSksCiAgICAgIEF1c2xhc3R1bmcgPSBmb3JtYXR0ZXIoInNwYW4iLAogICAgICAgIHN0eWxlID0geCB+IHN0eWxlKGNvbG9yID0gaWZlbHNlKHggPCAxLCAiZ3JlZW4iLCAicmVkIikpLAogICAgICAgIHggfiBpY29udGV4dChpZmVsc2UoeCA8IDEsICJvayIsICJyZW1vdmUiKSwgcGVyY2VudCh4KSkKICAgICAgKSwKICAgICAgYFdlZyAow5gpYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKSwKICAgICAgYFdlZyAobWluKWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIiksCiAgICAgIGBXZWcgKG1heClgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpCiAgICApCiAgKQpgYGAKCgojIyBEYXRlbiBmw7xyIGRpZSBBcHAgc3BlaWNoZXJuCgotIGVudGl0aWVzLmdlb2pzb24KLSBlbnRpdGllcy5jc3YKLSB1bml0cy5nZW9qc29uCi0gdW5pdHMuY3N2Ci0gd2VpZ2h0cy5jc3YKLSBhc3NpZ25tZW50LmNzdgoKIyMjIE5ldWUgRGF0ZW4KCgojIyMjIEVudGl0aWVzIC8gU2NodWxlbgoKYGBge3J9CmVudGl0aWVzID0gc3Vic2V0KHJlX3NjaHVsc3RhbmRfZGYsIHNwYXRpYWxfbmFtZSAlaW4lIHJlbGV2YW50X3NjaG9vbHMpICU+JQogIHJlbmFtZShlbnRpdHlfaWQgPSBzcGF0aWFsX25hbWUpICU+JQogIHNlbGVjdCgtZ21sX2lkLCAtc3BhdGlhbF9hbGlhcywgLXNwYXRpYWxfdHlwZSkgJT4lCiAgaW5uZXJfam9pbihyZW5hbWUocmVsZXZhbnRfa2FwYXMsIGNhcGFjaXR5PUthcGEpLCBieT1jKCdlbnRpdHlfaWQnPSdTY2h1bG51bW1lcicpKQpjb29yZGluYXRlcyhlbnRpdGllcykgPSB+IGxvbiArIGxhdApwcm9qNHN0cmluZyhlbnRpdGllcykgPSBDUlMoIitwcm9qPWxvbmdsYXQgK2VsbHBzPVdHUzg0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyIpCmZpbGUucmVtb3ZlKCdhcHAvZGF0YS9lbnRpdGllcy5nZW9qc29uJykKZW50aXRpZXMgJT4lIHdyaXRlT0dSKCdhcHAvZGF0YS9lbnRpdGllcy5nZW9qc29uJywgbGF5ZXI9ImVudGl0aWVzIiwgZHJpdmVyPSJHZW9KU09OIiwgY2hlY2tfZXhpc3RzPUYpCmVudGl0aWVzQGRhdGEgJT4lIHNlbGVjdChlbnRpdHlfaWQsIGNhcGFjaXR5KSAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS9lbnRpdGllcy5jc3YnKQpgYGAKCiMjIyMgVW5pdHMgLyBTdGF0aXN0aXNjaGUgQmzDtmNrZQoKYGBge3J9CnVuaXRzID0gc3Vic2V0KGJsaywgQkVaID09IGJlel9pZCkKCnVuaXRzQGRhdGEkUExSID0gTlVMTAp1bml0c0BkYXRhJEVpbncgPSBOVUxMCnVuaXRzQGRhdGEkQkVaID0gTlVMTAp1bml0c0BkYXRhJHVuaXRfaWQgPSB1bml0c0BkYXRhJEJMSwp1bml0c0BkYXRhJEJMSyA9IE5VTEwKCnVuaXRzID0gdW5pdHMgJT4lIHNwOjptZXJnZSgKICBvcHRpbV9raWRzX2luX2Jsa3MgJT4lCiAgICBsZWZ0X2pvaW4oc2diSUlfYmxrKSAlPiUKICAgIHNlbGVjdCh1bml0X2lkPUJMSywgcG9wdWxhdGlvbj1raWRzLCBzZ2JJSXU2NSkKICApCgpmaWxlLnJlbW92ZSgnYXBwL2RhdGEvdW5pdHMuZ2VvanNvbicpCnVuaXRzICU+JSB3cml0ZU9HUignYXBwL2RhdGEvdW5pdHMuZ2VvanNvbicsIGxheWVyPSJ1bml0cyIsIGRyaXZlcj0iR2VvSlNPTiIsIGNoZWNrX2V4aXN0cz1GKQp1bml0c0BkYXRhICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL3VuaXRzLmNzdicpCmBgYAoKIyMjIyBadW9yZG51bmcKCmBgYHtyfQphc3NpZ25tZW50ID0gc29sdXRpb24gJT4lIHNlbGVjdCh1bml0X2lkPUJMSywgZW50aXR5X2lkPXNjaG9vbCkKYXNzaWdubWVudCAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS9hc3NpZ25tZW50LmNzdicpCmBgYAoKIyMjIyBXZWlnaHRzIC8gU2NodWx3ZWdlCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyAlPiUKICByZW5hbWUodW5pdF9pZD1CTEssIGVudGl0eV9pZD1kc3QpICU+JQogICNnYXRoZXIod2VpZ2h0LCB2YWx1ZSwgLXVuaXRfaWQsIC1lbnRpdHlfaWQpICU+JQogIHdyaXRlX2NzdignYXBwL2RhdGEvd2VpZ2h0cy5jc3YnKQpgYGAKCiMjIyMgWnVzw6R0emxpY2hlIERhdGVuCgpgYGB7cn0KZmlsZS5jb3B5KCdkb3dubG9hZC9SQlNfT0RfQkVaXzIwMTVfMTIuZ2VvanNvbicsICdhcHAvZGF0YS9SQlNfT0RfQkVaXzIwMTVfMTIuZ2VvanNvbicpCmBgYAoKYGBge3J9CmFkZHJlc3Nlc19pbl9iZXogPSBIS09fMjAxNVshaXMubmEob3ZlcihIS09fMjAxNSwgc3Vic2V0KGJsaywgQkVaID09IGJlel9pZCkpJEJFWiksYygnU1ROJywgJ0hOUicpXQpuYW1lcyhhZGRyZXNzZXNfaW5fYmV6KSA9IGMoJ3N0cmVldCcsICdubycpCmFkZHJlc3Nlc19pbl9iZXpAZGF0YQoKZmlsZS5yZW1vdmUoJ2FwcC9kYXRhL2FkZHJlc3Nlcy5nZW9qc29uJykKd3JpdGVPR1IoYWRkcmVzc2VzX2luX2JleiwgImFwcC9kYXRhL2FkZHJlc3Nlcy5nZW9qc29uIiwgbGF5ZXI9ImFkZHJlc3NlcyIsIGRyaXZlcj0iR2VvSlNPTiIsIGNoZWNrX2V4aXN0cyA9IEZBTFNFKQpgYGAKCg==